# -*- coding: utf-8 -*- 
"""
Project: AppleSnApi
Creator: SF
Create time: 2025-06-16 11:53
IDE: PyCharm
Introduction: API
"""
import json
import os
import sys
import time
import uuid
from datetime import datetime, timedelta
from typing import Optional
import uvicorn

from fastapi.responses import JSONResponse
from fastapi import FastAPI, Request
from loguru import logger
from pydantic import BaseModel, Field
from pymongo.errors import DuplicateKeyError

current_path = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(current_path)[0]
sys.path.append(rootPath)

from settings import LOG_DIR
from tools.DbConnect import redis_client, sn_collection
from tools.helper import validate_sn_code, get_system_info

# 声明实例
app = FastAPI(
    title="苹果SN查询系统",
    version="1.0",
    summary="基于苹果官网序列号查询功能实现",
    description="提供苹果设备序列号查询服务"
)

# 配置日志
logger.add(
    os.path.join(LOG_DIR, 'apple.log'),
    rotation="500 MB",
    serialize=True,
    level='INFO',
    format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}",
    filter=lambda record: record["extra"].get("source") == "apple",
    encoding='utf-8'
)

apple_logger = logger.bind(source="apple")

# 系统 uvicorn 日志路径
uvicorn_config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": "%(asctime)s %(threadName)s %(levelprefix)s  %(message)s",
            "use_colors": None
        },
        "access": {
            "()": "uvicorn.logging.AccessFormatter",
            "fmt": "%(asctime)s %(threadName)s %(levelprefix)s %(client_addr)s - '%(request_line)s' %(status_code)s"
        }
    },
    "handlers": {
        "default": {
            "formatter": "default",
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stderr"
        },
        "default_file": {
            "formatter": "default",
            "class": "logging.handlers.TimedRotatingFileHandler",
            "filename": os.path.join(LOG_DIR, 'app.log'),
            "when": "midnight",
            "encoding": "utf-8",
            "backupCount": 10
        },
        "access": {
            "formatter": "access",
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        },
        "access_file": {
            "formatter": "access",
            "class": "logging.handlers.TimedRotatingFileHandler",
            "filename": os.path.join(LOG_DIR, 'access.log'),
            "when": "midnight",
            "encoding": "utf-8",
            "backupCount": 10
        }
    },
    "loggers": {
        "uvicorn": {
            "handlers": ["default", "default_file"],
            "level": "INFO",
            "propagate": False
        },
        "uvicorn.error": {"level": "INFO"},
        "uvicorn.access": {
            "handlers": ["access", "access_file"],
            "level": "INFO",
            "propagate": False
        }
    }
}

# 配置需要认证的路径
AUTH_REQUIRED_PATHS = [
    "/api/sn/query"
]

# 查询间隔时间
REPEAT_QUERY_INTERVAL = timedelta(minutes=60)


# 比较两个时间字符串的差值是否超过REPEAT_QUERY_INTERVAL
def compare_time_intervals(time_str1, time_str2):
    """
    比较两个时间字符串的差值是否超过REPEAT_QUERY_INTERVAL
    :param time_str1: 第一个时间字符串，格式"%Y-%m-%d %H:%M:%S"
    :param time_str2: 第二个时间字符串，格式"%Y-%m-%d %H:%M:%S"
    :return: 如果差值大于等于REPEAT_QUERY_INTERVAL返回True，否则返回False
    """
    time_format = "%Y-%m-%d %H:%M:%S"
    time1 = datetime.strptime(time_str1, time_format)
    time2 = datetime.strptime(time_str2, time_format)
    time_diff = abs(time1 - time2)
    return time_diff >= REPEAT_QUERY_INTERVAL


# 请求参数数据模型
class SNRequest(BaseModel):
    sn: str = Field(..., title='SN 码', description='Apple 设备序列号')
    user: Optional[str] = None
    bs_id: Optional[str] = None


# 中间件 - 认证
@app.middleware("https")
async def authenticate_request(request: Request, call_next):
    # 检查请求路径是否需要认证
    # if request.url.path not in AUTH_REQUIRED_PATHS:
    #     # 不需要认证的路径直接放行
    #     return await call_next(request)

    # xtoken = request.query_params.get("xtoken")
    # secret = request.query_params.get("secret")

    # if xtoken != XTOKEN or secret != SECRET:
    # if xtoken != XTOKEN:
    #     apple_logger.warning(f"认证失败: 无效的token或secret, IP: {request.client.host}")
    #     return JSONResponse(
    #         content={"error": "认证失败"},
    #         status_code=status.HTTP_401_UNAUTHORIZED
    #     )

    start_time = time.time()  # 记录请求开始时间

    # 获取请求参数
    request_params = {}
    if request.method == "GET":
        request_params = dict(request.query_params)
    elif request.method == "POST":
        try:
            request_params = await request.json()
        except:
            form_data = await request.form()  # 如果不是JSON格式，尝试获取表单数据
            request_params = dict(form_data)

    # 校验参数(仅对/api/sn/query)
    if request.url.path in AUTH_REQUIRED_PATHS:
        if not request_params:
            return JSONResponse(
                content={"status": 'failed', "message": "request body required"},
                status_code=200
            )

        if 'sn' not in request_params:
            return JSONResponse(
                content={"status": 'failed', "message": "sn Field required"},
                status_code=200
            )

    # 处理请求
    try:
        response = await call_next(request)
    except Exception as e:
        apple_logger.error(f"中间件-未处理的异常: {str(e)}")
        return JSONResponse(
            content={'status': "failed", "message": f"未处理的异常:{str(e)}"},
            status_code=200
        )

    # 记录请求指标
    process_time = time.time() - start_time

    # 记录请求日志，包括请求参数
    apple_logger.info(
        f"请求处理完成: {request.method} {request.url.path}, "
        f"状态码: {response.status_code}, 耗时: {process_time:.2f}s, "
        f"请求参数: {request_params}"
    )

    return response


# 查询任务提交&查询接口
@app.post("/api/sn/query", response_model=dict)
async def submit_sn_query(sn_request: SNRequest):
    """提交SN序列号查询请求，支持任务重新下发与ID更新,结果直接查询"""
    try:
        try:
            validate_sn_code(sn_request.sn)
        except Exception as e:
            return {'status': "failed", "message": f"SN校验错误:{str(e)}"}

        # 查询数据库中是否存在该SN的记录
        existing_record = sn_collection.find_one({"sn": sn_request.sn}, {'_id': 0})

        # 情况1：SN已存在于数据库
        if existing_record:
            status = existing_record.get("status", "unknown")
            type_code = existing_record.get("typeCode")
            last_query_time = existing_record.get("ss")  # 上次查询时间

            # 存在查询记录,且已完成查询&查询结果为已激活或SN无效,无需重新查询
            if status == 'complete' and type_code in ["10001", "10003"]:
                apple_logger.info(f"SN: {sn_request.sn} 历史记录有效,无需重新查询,直接返回")
                return {
                    "status": "success",
                    "message": "查询成功",
                    "sn": sn_request.sn,
                    "data": existing_record,
                    "is_new": False,
                    "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                }
            # 存在查询记录,但状态为 pending/processing ,后台任务还未完成查询,则返回已有任务id
            elif status in ["pending", "processing"]:
                apple_logger.info(f"SN: {sn_request.sn} 已存在待处理任务，返回任务ID")
                return {
                    "status": "success",
                    "message": "已有相同SN的查询任务正在处理中",
                    "task_id": existing_record["task_id"],
                    "sn": sn_request.sn,
                    "is_new": False,
                    "created_at": existing_record["created_at"]
                }
            # 存在查询记录,但上次查询结果该SN为未激活状态,
            elif status == "complete" and type_code == "10002":
                time_diff = compare_time_intervals(last_query_time, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                if not time_diff:
                    # 当前时间与上一次查询间隔时间在1小时内的,则直接返回最近1次查询结果
                    apple_logger.info(f"SN: {sn_request.sn} 在重复查询时间间隔内，返回最近结果")
                    return {
                        "status": "success",
                        "message": f"最近1小时内的查询结果",
                        "sn": sn_request.sn,
                        "data": existing_record,
                        "is_new": False,
                        "time": last_query_time  # 上一次的查询时间
                    }

                # 超过重复查询间隔(1小时)，允许重新查询
                apple_logger.info(f"SN: {sn_request.sn} 未激活且超过重复查询时间间隔，生成新的查询任务")

                # 生成新任务ID
                new_task_id = uuid.uuid4().hex.lower()
                updated_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

                # 构建更新数据
                update_data = {
                    "task_id": new_task_id,
                    "user": sn_request.user or existing_record.get("user"),
                    "bs_id": sn_request.bs_id or existing_record.get("bs_id"),
                    "status": "pending",
                    "updated_at": updated_at
                }

                # 只更新提供了新值的字段
                if sn_request.user is not None:
                    update_data["user"] = sn_request.user
                if sn_request.bs_id is not None:
                    update_data["bs_id"] = sn_request.bs_id

                # 更新数据库
                sn_collection.update_one(
                    {"sn": sn_request.sn},
                    {"$set": update_data}
                )

                # 构建新任务数据
                new_task_data = {
                    "task_id": new_task_id,
                    "sn": sn_request.sn,
                    "user": update_data["user"],
                    "bs_id": update_data["bs_id"],
                    "created_at": updated_at,  # 使用更新时间作为创建时间
                    "status": "pending"
                }

                # 添加到Redis队列
                redis_client.rpush("sn_query_tasks", json.dumps(new_task_data, ensure_ascii=False))
                apple_logger.info(f"SN: {sn_request.sn} 新任务已生成并下发，任务ID: {new_task_id}")

                return {
                    "status": "success",
                    "message": "未激活设备查询任务已重新提交",
                    "task_id": new_task_id,
                    "sn": sn_request.sn,
                    "user": update_data["user"],
                    "bs_id": update_data["bs_id"],
                    "is_new": True,  # 视为新任务
                    "updated_at": updated_at
                }
            # 首次查询为失败的情况,
            elif status == 'failed':  # 查询失败的允许重试
                time_diff = compare_time_intervals(last_query_time, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                if not time_diff:
                    # 当前时间与上一次查询间隔时间在1小时内的,则直接返回最近1次查询结果
                    apple_logger.info(f"SN: {sn_request.sn} 在重复查询时间间隔内，返回最近结果")
                    return {
                        "status": "success",
                        "message": f"最近1小时内的查询结果",
                        "sn": sn_request.sn,
                        "data": existing_record,
                        "is_new": False,
                        "time": last_query_time  # 上一次的查询时间
                    }

                # 超过重复查询间隔(1小时)，允许重新查询
                apple_logger.info(f"SN: {sn_request.sn} 首次查询失败且超过重复查询时间间隔，生成新的查询任务")

                # 生成新任务ID
                new_task_id = uuid.uuid4().hex.lower()
                updated_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

                # 构建更新数据
                update_data = {
                    "task_id": new_task_id,
                    "user": sn_request.user or existing_record.get("user"),
                    "bs_id": sn_request.bs_id or existing_record.get("bs_id"),
                    "status": "pending",
                    "updated_at": updated_at
                }

                # 只更新提供了新值的字段
                if sn_request.user is not None:
                    update_data["user"] = sn_request.user
                if sn_request.bs_id is not None:
                    update_data["bs_id"] = sn_request.bs_id

                # 更新数据库
                sn_collection.update_one(
                    {"sn": sn_request.sn},
                    {"$set": update_data}
                )

                # 构建新任务数据
                new_task_data = {
                    "task_id": new_task_id,
                    "sn": sn_request.sn,
                    "user": update_data["user"],
                    "bs_id": update_data["bs_id"],
                    "created_at": updated_at,  # 使用更新时间作为创建时间
                    "status": "pending"
                }

                # 添加到Redis队列
                redis_client.rpush("sn_query_tasks", json.dumps(new_task_data, ensure_ascii=False))
                apple_logger.info(f"SN: {sn_request.sn} 新任务已生成并下发，任务ID: {new_task_id}")

                return {
                    "status": "success",
                    "message": "查询失败设备重试任务已重新提交",
                    "task_id": new_task_id,
                    "sn": sn_request.sn,
                    "user": update_data["user"],
                    "bs_id": update_data["bs_id"],
                    "is_new": True,  # 视为新任务
                    "updated_at": updated_at
                }
            # 存在查询记录,但非上面几种情况(异常情况)
            else:
                apple_logger.info(f"SN: {sn_request.sn} 存在历史查询，返回历史结果")
                return {
                    "status": "success",
                    "message": "查询成功",
                    "sn": sn_request.sn,
                    "data": existing_record,
                    "is_new": False,
                    "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                }

        # 情况2：SN不存在或其他情况，创建新任务
        task_id = uuid.uuid4().hex.lower()
        created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # 构建任务数据
        task_data = {
            "task_id": task_id,
            "sn": sn_request.sn,
            "user": sn_request.user,
            "bs_id": sn_request.bs_id,
            "created_at": created_at,
            "status": "pending"
        }

        # 使用原子操作确保任务唯一性
        result = sn_collection.update_one(
            {"sn": sn_request.sn},
            {"$setOnInsert": task_data},
            upsert=True
        )

        # 检查是否成功插入新任务
        if result.upserted_id or not existing_record:
            # 新任务插入成功或SN不存在，添加到Redis队列
            redis_client.rpush("sn_query_tasks", json.dumps(task_data, ensure_ascii=False))
            apple_logger.info(f"SN: {sn_request.sn} 新查询任务已添加，任务ID: {task_id}")

            return {
                "status": "success",
                "message": "查询任务已提交",
                "task_id": task_id,
                "sn": sn_request.sn,
                "user": sn_request.user,
                "bs_id": sn_request.bs_id,
                "created_at": created_at,
                "is_new": True
            }
        else:
            # 这种情况理论上不会发生，因为前面已有详细检查
            apple_logger.warning(f"SN: {sn_request.sn} 出现未知状态，返回已有任务")
            existing_task = sn_collection.find_one({"sn": sn_request.sn}, {'_id': 0})
            return {
                "status": "success",
                "message": "已有相同SN的查询任务",
                "task_id": existing_task["task_id"],
                "sn": sn_request.sn,
                "is_new": False,
                "created_at": existing_task["created_at"]
            }
    except DuplicateKeyError:
        # 处理并发插入冲突
        apple_logger.warning(f"并发插入冲突: SN {sn_request.sn} 已有任务")
        existing_task = sn_collection.find_one({"sn": sn_request.sn}, {'_id': 0})
        return {
            "status": "success",
            "message": "已有相同SN的查询任务",
            "task_id": existing_task["task_id"],
            "sn": sn_request.sn,
            "is_new": False,
            "created_at": existing_task["created_at"]
        }
    except Exception as e:
        apple_logger.error(f"提交查询失败: {str(e)}")
        return {'status': "failed", "message": f"服务器内部错误:{str(e)}"}


# 任务状态查询接口
@app.get("/api/sn/task/{task_id}", response_model=dict)
async def get_task_status(task_id: str):
    """查询任务状态"""
    try:
        task = sn_collection.find_one({"task_id": task_id}, {'_id': 0})
        if not task:
            return {'status': "failed", "message": f"task:{task_id} 不存在"}

        return {
            "task_id": task["task_id"],
            "sn": task.get("sn"),
            "status": task.get("status", "unknown"),
            "created_at": task.get("created_at", ""),
            "updated_at": task.get("updated_at", ""),
            "ss": task.get("ss", "")
        }
    except Exception as e:
        apple_logger.error(f"查询任务状态失败: {str(e)}")
        return {'status': "failed", "message": f"服务器内部错误:{str(e)}"}


# 统计当日新增数量和近3日新增数量
def count_success_num():
    # 获取当前日期（精确到天）
    today = datetime.now().date()
    today_str = today.strftime("%Y-%m-%d")  # 例如：2025-06-23

    # 计算近3天的起始日期（今天 - 3天，包含今天）
    three_days_ago = (today - timedelta(days=3))
    three_days_ago_str = datetime.strftime(three_days_ago, "%Y-%m-%d")

    # 当天的时间范围：今天 00:00:00 至 今天 23:59:59
    today_start = f"{today_str} 00:00:00"
    today_end = f"{today_str} 23:59:59"

    # 近3天的时间范围：3天前 00:00:00 至 今天 23:59:59
    three_days_start = f"{three_days_ago_str} 00:00:00"
    three_days_end = today_end

    # 当天完成量查询（使用$gte和$lte精确匹配，避免正则可能的问题）
    today_query = {"status": "complete", "ss": {"$gte": today_start, "$lte": today_end}}
    today_count = sn_collection.count_documents(today_query)

    # 近3天完成量查询
    three_days_query = {"status": "complete", "ss": {"$gte": three_days_start, "$lte": three_days_end}}
    three_days_count = sn_collection.count_documents(three_days_query)

    # 未激活设备
    type10002_query = {'typeCode': '10002'}
    type10002_count = sn_collection.count_documents(type10002_query)

    return today_count, three_days_count, type10002_count


# 健康检查接口
@app.get("/api/health")
async def health_check():
    """健康检查"""
    status_data = {}
    try:
        system_info = get_system_info(target_process='main.py')  # server
        hash_data = redis_client.hgetall('apple_sn_counter')
        node_info = []
        for k, v in hash_data.items():
            if k.startswith("worker:"):
                node_info.append(json.loads(v))
            else:
                status_data[k] = v

        # 查询当日和近3日新增数量和未激活数量
        today_count, three_days_count, type10002_count = count_success_num()

        status_data['today_count'] = int(today_count)
        status_data['three_days_count'] = int(three_days_count)
        status_data['type10002_count'] = int(type10002_count)  # 未激活数量
        status_data['server'] = system_info
        status_data['worker'] = node_info if node_info else None  # worker

        return status_data
    except Exception as e:
        apple_logger.error(f"健康检查失败: {str(e)}")
        return {'status': "failed", "message": f"服务器内部错误:{str(e)}"}


if __name__ == "__main__":
    '''linux下执行启动:nohup python3 main.py ./log/apple.log 2>&1 &'''
    uvicorn.run(app='main:app', host='0.0.0.0', port=12356, reload=True, workers=4, log_config=uvicorn_config)
